package com.espirit.eap.manager;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;

import com.espirit.eap.sa.Employee;
import com.espirit.eap.sa.UserServiceSupport;
import com.espirit.eap.util.StatGenericJPAService;
import com.googlecode.cswish.Config;
import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.ParameterMap;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.Condition;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.conversion.PageElementService;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.SearchCallback;
import com.googlecode.cswish.util.bean.ModelUtil;

//@Service
@Transactional
public class DepartmentServiceSupport {

	@Resource
	StatGenericJPAService genericService;
	
	@Resource
	Config config;
	
	
	@PersistenceContext(unitName="espirit")
	private EntityManager em;
	
	@Resource
	private PageElementService pageElementService;
	
	@Resource
	CacheService cacheService;
	
	@Resource
	UserServiceSupport userService;
	
	Map<String , Integer> deptCache ;
	Map<String, Set> deptIdsCache; //String deptid, set: child deptids
	@PostConstruct
	private void init() {
		deptCache = cacheService.newCache(Integer.class);
		deptIdsCache = cacheService.newCache(Set.class);
	}
	
	public Department add(Department model){
		model = genericService.add(model);
//		onChanged(model.getId());
		reCalMap();
		return model;
	}
	
	@Async
	private void reCalMap(){
		Department example = new Department();
		List<Department> results = genericService.searchAll(example).getData();
		for(Department dept : results){
			Set<Integer> childIds = getChildIds(dept);
			Set<Integer> childrenIds = getChildrenIds(dept);
			if (childIds != null && !childIds.isEmpty()){
				StringBuilder sb = new StringBuilder();
				for(Integer id : childIds){
					sb.append(id.toString()).append(",");
				}
				sb.append(dept.getId().toString());
				dept.setChildIds(sb.toString());
			} else {
				dept.setChildIds(dept.getId().toString());
			}
			
			if (childrenIds != null && !childrenIds.isEmpty()){
				StringBuilder sb = new StringBuilder();
				for(Integer id : childrenIds){
					sb.append(id.toString()).append(",");
				}
				sb.append(dept.getId().toString());
				dept.setChildrenIds(sb.toString());
			} else {
				dept.setChildrenIds(dept.getId().toString());
			}
			genericService.merge(dept);
		}
		
		userService.reCalcMap();
	}
	
	private Set<Integer> getChildrenIds(Department dept){
		if (dept == null ) return Collections.EMPTY_SET;
		String ql = "from " + Department.class.getName() + " a where a.parentId=?";
		QLInfo qlInfo = new QLInfo(ql, true, dept.getId());
		
		List<Department> depts = genericService.searchByQl(qlInfo, false).getData();
		if (depts != null && !depts.isEmpty()){
			Set<Integer> sets = new HashSet<Integer>();
			for(Department model : depts){
				sets.add(model.getId());
				sets.addAll(getChildrenIds(model));
			}
			return sets;
		} else {
			return Collections.EMPTY_SET;
		}
	}
	
	private Set<Integer> getChildIds(Department dept){
		String ql = "from " + Department.class.getName() + " a where a.parentId=?";
		QLInfo qlInfo = new QLInfo(ql, true, dept.getId());
		List<Department> depts = genericService.searchByQl(qlInfo, false).getData();
		if (depts != null && !depts.isEmpty()){
			Set<Integer> sets = new HashSet<Integer>();
			for(Department model : depts){
				sets.add(model.getId());
			}
			return sets;
		} else {
			return Collections.EMPTY_SET;
		}
	}
	
	public void update(Department model){
		Set<String> enabledFields = new HashSet<String>(Arrays.asList("name"));
		genericService.update(model, false, enabledFields, null);
		
		onChanged(model.getId());
	}
	
	private void onChanged(Integer deptId){
		String key = deptId.toString();
		deptIdsCache.remove(key);
		
//		getAllDescDeptIds(deptId);
	}
	
	@Transactional
    public boolean deleteList(List<Department> results) {
    	boolean ret = true;
    	if (results != null) {
			for (Department department : results) {
				if (department != null) {
					ret &= genericService.delete(department);
				}
			}
    	}
    	
    	reCalMap();
    	
    	return ret;
    }
	
	public PageInfo<Department> search(Department model, String keywords,
			int start, int limit, String sort, String dir, boolean newSearch,
			Reference<QLInfo> qlInfoRef, @ParamScope(visible=false, value="#ps")PageElements ps){
		if (newSearch) {
			PageElements psAdd = pageElementService.getPageElements(Department.class.getName(), "_add");
			psAdd.addExtraName("model.parentId", null, null);
			psAdd.addExtraName("model.name", null, null);
		}
		
		limit = 10000;
		PageInfo<Department> pageInfo = genericService.search(model, start, limit, sort, dir, newSearch, qlInfoRef);
		return pageInfo;
	}

	
	public Integer getDeptIdByName(String deptName) {
		if (deptName == null ) return null;
    	if (deptCache.get(deptName) == null){
    		Department search = new Department();
			search.setName(deptName);
			Department theDept = genericService.searchAll(search).getData(0);
			if (theDept == null){
				return null;
			}
			deptCache.put(deptName, theDept.getId());
    	}
    	return deptCache.get(deptName);
	}
	
	@Rest(simpleServiceName="loginUser", type="free")
	public List<Department> getAllDepartments(){
		String hql = "from " + Department.class.getName() + " a ";
		return genericService.searchByQl(new QLInfo(hql, false)).getData();
	}
	
	@Rest(simpleServiceName="loginUser", type="free")
	public List<Employee> getValidEmployees(Integer deptId){
		Employee example = new Employee();
		if (deptId != null){
			ModelUtil.setProperty(example, "department.id", deptId);
		}
		example.setActive(Employee.ACTIVE_YES);
		return genericService.searchByModel(example, Condition.DEFAULT).getData();
	}
	
	@Rest(simpleServiceName="loginUser", type="free")
	public Set<Employee> getValidDescEmployees(Integer deptId){
		Set<Employee> employees = new HashSet<Employee>();
		if (deptId == null){
			List<Employee> results = getValidEmployees(deptId);
			if (results != null){
				for(Employee emp : results){
					employees.add(emp);
				}
			}
		} else {
			Set<Integer> lists = getChildrenIds(deptId);
			for(Integer departmentId : lists){
				List<Employee> results = getValidEmployees(departmentId);
				if (results != null){
					for(Employee emp : results){
						employees.add(emp);
					}
				}
			}
		}
		return employees;
	}
	
	private Set<Integer> getValidDescEmployeeIds(Integer deptId){
		Set<Integer> employeeIds = new HashSet<Integer>();
		Set<Employee> employees = getValidDescEmployees(deptId);
		if (employees == null) return Collections.EMPTY_SET;
		for(Employee emp : employees){
			employeeIds.add(emp.getId());
		}
		return employeeIds;
	}
	
	public Set<Integer> getChildrenIds(Integer deptId) {
		Department dept = genericService.load(Department.class, deptId);
		String childrenIds = dept.getChildrenIds();
		if (childrenIds == null) return Collections.EMPTY_SET;
		
		String[] ids = childrenIds.split(",");
		List<String> lists = Arrays.asList(ids);
		Set<Integer> sets = new HashSet<Integer>();
		for(String id : lists){
			sets.add(Integer.parseInt(id));
		}
		return sets;
	}
	
	public Set<Integer> getAllChildrenIds(String[] deptIds){
		if (deptIds == null) return Collections.EMPTY_SET;
		Set<Integer> ids = new HashSet<Integer>();
		for(String deptId : deptIds){
			Set<Integer> results = getChildrenIds(Integer.parseInt(deptId));
			ids.addAll(results);
		}
		return ids;
	}
	
	public Set<Integer> getAllValidEmployeeIds(String[] deptIds, Integer empId) {
		Set<Integer> results = new HashSet<Integer>();
		if (empId != null ){
			Employee emp = genericService.load(Employee.class, empId);
			results.add(emp.getId());
			return results;
		} else {
			if (deptIds != null && deptIds.length > 0){
				for(String deptId : deptIds){
					results.addAll(getValidDescEmployeeIds(Integer.valueOf(deptId)));
				}
				return results;
			} else {
				return getValidDescEmployeeIds(null);
			}
		}
	}
	
	public boolean isChild(Integer from, Integer to){
		if (from == null && to == null) return false;
		if (from.equals(to)) return false;
		Set<Integer> ids = getChildrenIds(to);
		return ids.contains(from.toString());
	}
	
	public Department getParentDepartment(Integer deptId){
		Department child = genericService.load(Department.class, deptId);
		if (child == null || child.getParentId() == null) return null;
		return genericService.load(Department.class, child.getParentId());
	}
	
	public void buildQlInfo(QLInfo qlInfo, String deptIds){
		int index = qlInfo.getQl().indexOf(" where ");
		if (!StringUtils.isEmpty(deptIds)){
			String[] ids = deptIds.split(",");
			Set<Integer> deptIdSet = getAllChildrenIds(ids) ;
			StringBuilder sb = new StringBuilder();
			for(Integer id : deptIdSet){
				sb.append(id).append(",");
			}
			String elseWhere = (index >= 0 ? " and" : " where")
					+ " a.department.id in ("
					+ StringUtils.repeat("?,", deptIdSet.size()) ;
			elseWhere = elseWhere.substring(0, elseWhere.length() -1) + ")";
			String ql = qlInfo + elseWhere ;
			List params = new ArrayList<Integer>(deptIdSet.size());
			for (Integer id : deptIdSet) {
				params.add(id);
			}
			qlInfo.setParams(params);
			qlInfo.setQl(ql);
		}
	}
	
	
	public SearchCallback buildDeptDefaultSearchCallback(final ParameterMap model, final String sort, final String dir){
		final String deptIds = model.getParameter("deptIds");
		SearchCallback callback = new SearchCallback() {
			@Override
			public QLInfo afterBuildQL(boolean newSearch, QLInfo qlInfo) {
				if (newSearch &&  qlInfo.getQl().length() > 0) {
					buildQlInfo(qlInfo, deptIds);
				} 
				SearchCallback searchCallback = genericService.createDefaultSearchCallback(model.getModelName(), model, sort, dir);
				searchCallback.afterBuildQL(newSearch, qlInfo);
				return qlInfo;
			}
		};
		return callback;
	}
	
	@Transactional
	public void importData(Map<String, String> data){
		if (data == null) return ;
		Department example = new Department();
		example = genericService.searchByModel(example, Condition.SEARCH_ONE).getData(0);
		if (example != null) return ;
		
		for(String key : data.keySet()){
			
			String name = key;
			if (StringUtils.isEmpty(name)) continue;
			
			String parentName = data.get(key);
			addDept(name, parentName, data);
		}
		
//		example = new Department();
//		List<Department> depts = genericService.searchAll(example).getData();
//		if (depts != null){
//			for (Department dept : depts){
//				onChanged(dept.getId());
//			}
//		}
	}
	
	private Department addDept(String name, String parentName, Map<String, String> data){
		Integer id = getDeptIdByName(name);
		if (id == null){
			Department dept = new Department();
			dept.setName(name);
			dept = genericService.add(dept);
			
			Department parent = null;
			Integer parentId = null;
			if (!StringUtils.isEmpty(parentName)){
				parentId = getDeptIdByName(parentName);
				if (parentId == null){
					parent = addDept(parentName, data.get(parentName), data);
					parentId = parent.getId();
				}
			}
			if (parentId != null){
				parent = genericService.load(Department.class, parentId);
				if (!parent.getName().equals(dept.getName())){
					dept.setParentId(parent.getId());
					genericService.merge(dept);
				}
			}
			return dept;
		} else {
			return genericService.load(Department.class, id);
		}
	}
	
	
	public void initData(){
		Department example = new Department();
		example = genericService.searchByModel(example, Condition.SEARCH_ONE).getData(0);
		if (example != null) return ;
		
		FileInputStream input = null;
		try {
			String licenseFile = config.getRealWebRootPath() + "/department.xlsx";
			File file = new File(licenseFile);
			input = new FileInputStream(file);
			Workbook book;
			if (file.getName().endsWith("xls")) {
				book = new HSSFWorkbook(new POIFSFileSystem(input));
			} else {
				book = new XSSFWorkbook(input);
			}
			Sheet sheet = book.getSheetAt(0);
			int rowCount = sheet.getPhysicalNumberOfRows();
			List<Department> data = new ArrayList<Department>();
			for (int i = 1; i <= rowCount ; i++){
				Row sheetRow = sheet.getRow(i);
				if (sheetRow == null) continue;
				String name = sheetRow.getCell(0).getStringCellValue();
				String parentName = sheetRow.getCell(1).getStringCellValue();
				
				if (StringUtils.isEmpty(name)) break;
				
				Department dept = new Department();
				dept.setName(name);
				dept.setParentName(parentName);
				data.add(dept);
				
			}
			
			importData(data);
			
		} catch (Exception ex){
			System.out.println(ex);
		}
		
	}
	
	/*
	 * data.code = name
	 * data.name = parentName
	 */
	@Transactional
	public void importData(List<Department> data){
		if (data == null) return ;
		for(Department dept : data){
			String name = dept.getName();
			if (StringUtils.isEmpty(name)) continue;
			
			String parentName = dept.getParentName();
			addDept(name, parentName, data);
		}
		
		reCalMap();
		
	}
	
	private Department addDept(String name, String parentName, List<Department> data){
		Integer id = getDeptIdByName(name);
		if (id == null){
			Department dept = new Department();
			dept.setName(name);
			dept = genericService.add(dept);
			
			Department parent = null;
			Integer parentId = null;
			if (!StringUtils.isEmpty(parentName)){
				parentId = getDeptIdByName(parentName);
				if (parentId == null){
					parent = addDept(parentName, getParentName(parentName, data) , data);
					parentId = parent.getId();
				}
			}
			if (parentId != null){
				parent = genericService.load(Department.class, parentId);
				if (!parent.getName().equals(dept.getName())){
					dept.setParentId(parent.getId());
					genericService.merge(dept);
				}
			}
			return dept;
		} else {
			return genericService.load(Department.class, id);
		}
	}
	
	private String getParentName(String name, List<Department> data){
		for(Department dept : data){
			if (dept.getName().equals(name)) return dept.getParentName();
		}
		return null;
	}
	
	public Department getRootDepartment(){
		String ql = "select a from " + Department.class.getName() + " a where a.parentId = null";
		Department root = (Department) genericService.searchByQl(new QLInfo(ql, true)).getData(0);
		return root;
	}
	
}
